/*
** ed.c
**
** Ed, Version 1.51, Copyright (c) 1992-94 SoftCircuits
** Redistributed by permission.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pictor.h>
#include "ed.h"

char hlp_file[] = "ED.HLP";
static int mainloop = TRUE;

/*
** displays an out-of-memory error message
** since messagebox creates a window using static memory,
** there is no danger that messagebox will fail due to lack of memery
*/
void outofmemory(void)
{
	messagebox("Out of memory","Error",MB_OK,&msgcolors);

} /* outofmemory */

/*
** expands the given line to accommodate the specified
** number of characters at the specified column
*/
int expand_line(LINE **line,int col,int count)
{
	LINE *new;

	/* resize buffer */
	new = realloc(*line,sizeof(LINE) + (*line)->len + count);

	if(new == NULL) { /* out of memory */
		outofmemory();
		return(FALSE);
	}

	/* shift characters to make room */
	memmove(new->text + col + count,new->text + col,new->len - col);
	new->len += count;

	/* if realloc() moved the buffer, update any */
	/* pointers that pointed to the old buffer */
	if(new != *line) {
		if(head == *line) head = new;
		if(tail == *line) tail = new;
		if(top_line == *line) top_line = new;

		if(new->prev) new->prev->next = new;
		if(new->next) new->next->prev = new;

		*line = new;
	}

	return(TRUE);

} /* expand_line */

/*
** shrinks the given line at the given column count bytes
** there is no return value since this function assumes realloc
** will succeed (since we are making the block smaller)
*/
void shrink_line(LINE **line,int col,int count)
{
	LINE *new;

	/* don't delete more characters than are in the line */
	if(count > (*line)->len)
		count = (*line)->len;

	/* shift characters */
	memmove((*line)->text + col,(*line)->text + col + count,
		(*line)->len - (col + count));

	/* resize buffer */
	new = realloc(*line,sizeof(LINE) + (*line)->len - count);
	new->len -= count;

	/* if realloc() moved the buffer, update any */
	/* pointers that pointed to the old buffer */
	if(new != *line) {
		if(head == *line) head = new;
		if(tail == *line) tail = new;
		if(top_line == *line) top_line = new;

		if(new->prev) new->prev->next = new;
		if(new->next) new->next->prev = new;

		*line = new;
	}

} /* shrink_line */

/*
** splits the current line at the current position
** into two separate lines
*/
void split_line()
{
	LINE *new;

	/* allocate new line */
	new = malloc(sizeof(LINE) + (curr_line->len - line_ndx));
	if(new == NULL) { /* out of memory */
		outofmemory();
		return;
	}

	add_to_list(new,curr_line);

	/* copy split portion of line to new line */
	new->len = (curr_line->len - line_ndx);
	memmove(new->text,curr_line->text + line_ndx,new->len);

	/* resize original line */
	shrink_line(&curr_line,line_ndx,new->len);

	/* move to start of new line */
	_down();
	line_ndx = 0;

	modified = TRUE;
	update_state = UPDATE_REPAINT;
	update_cursor(TRUE);

} /* split_line */

/*
** appends the next line to the current line
*/
void join_line()
{
	int i;

	/* if there is a next line */
	if(curr_line->next != NULL) {

		i = curr_line->len;

		/* make room for combined line */
		if(!expand_line(&curr_line,curr_line->len,curr_line->next->len))
			return;  /* out of memory */

		/* append next line to current line */
		memmove(curr_line->text + i,curr_line->next->text,
			curr_line->next->len);

		/* delete next line */
		remove_from_list(curr_line->next);

		modified = TRUE;
		update_state = UPDATE_REPAINT;
		update_cursor(TRUE);
	}

} /* join_line */

/*
** deletes the current line
*/
void delete_line()
{
	/* don't remove last line from list */
	if(curr_line->next == NULL) {
		if(curr_line->len > 0) {
			shrink_line(&curr_line,0,curr_line->len);
		}
	}
	else {
		curr_line = curr_line->next;
		remove_from_list(curr_line->prev);
	}
	line_ndx = 0;
	modified = TRUE;
	update_state = UPDATE_REPAINT;
	update_cursor(TRUE);

} /* delete_line */

/*
** deletes the character at the current position
*/
void delete_char()
{
	/* if anything at cursor to delete */
	if(line_ndx < curr_line->len) {
		shrink_line(&curr_line,line_ndx,1);

		/* show modified line */
		show_line(curr_line,(file_row - top_row) + edit_top);
		modified = TRUE;
		update_cursor(TRUE);
	}
	else join_line();    /* delete line break */

} /* delete_char */

/*
** deletes the character left of the current position
*/
void backspace_char()
{
	if(line_ndx > 0 || curr_line->prev != NULL) {
		_left();
		delete_char();
	}
} /* backspace_char */

/*
** inserts a character at the current file position
*/
void insert_char(char c)
{
	/* expand line if insert mode or at line end */
	if(line_ndx >= curr_line->len || insert_mode) {
		if(!expand_line(&curr_line,line_ndx,1))
			return;  /* error */
	}

	/* put character into buffer */
	curr_line->text[line_ndx] = c;

	/* show modified line */
	show_line(curr_line,(file_row - top_row) + edit_top);

	line_ndx++;
	modified = TRUE;
	update_cursor(TRUE);

} /* insert_char */

/*
** displays command line syntax and terminates
** with errorlevel 1
*/
void show_usage()
{
	printf("%s\n\n",copyright);

	printf("Usage:\tED <filename> [/b] [/s]\n");
	printf("\t/b Black and white (disables color on color systems)\n");
	printf("\t/s Snow check (write only during video retrace)\n");

	exit(1);

} /* show_usage */

/*
** causes the main loop to terminate by setting mainloop = FALSE
*/
void terminate(void)
{
	helpsetcontext(hlp_exit);
	mainloop = !save_modified();

} /* terminate */

/*
** here's main()
*/
int main(int argc,char *argv[])
{
	int i,key,blkwht = FALSE,snwchk = FALSE;
	char *fname = NULL;
	
	#ifdef EXT_ED
		int shiftkeys, discard_hilite = TRUE;
	#endif

	/* read command line arguments */
	for(i = 1;i < argc;i++) {
		switch(argv[i][0]) {
			case '/':
			case '-':
				switch(tolower(argv[i][1])) {
					case 'b':
						blkwht = TRUE;
						break;
					case 's':
						snwchk = TRUE;
						break;
					default:
						show_usage();
				}
				break;
			default:
				if(fname == NULL)
					fname = strupr(argv[i]);
				else
					show_usage();
		}
	}
	initvideo(); /* initialize library */
	getvconfig(&vcfg);

	if(snwchk)
		snowcheckoff();

	if(vcfg.colorsupport && !blkwht) {
		edit_color = foreback(BOLD|CYAN,BLUE);
		status_color = foreback(BLACK,CYAN);
		msgcolors.normal = foreback(BLACK,WHITE);
		msgcolors.boldnormal = foreback(BOLD|WHITE,WHITE);
		msgcolors.select = foreback(WHITE,BLACK);
		msgcolors.boldselect = foreback(BOLD|WHITE,BLACK);
		mnucolors.normal = foreback(BLACK,CYAN);
		mnucolors.boldnormal = foreback(BOLD|WHITE,CYAN);
		mnucolors.select = foreback(BOLD|CYAN,BLUE);
		mnucolors.boldselect = foreback(BOLD|WHITE,BLUE);
		_PL_shadowcolor = foreback(BOLD|BLACK,BLACK);
	}
	edit_top = 2;	edit_bottom = vcfg.rows - 1;
	edit_left = 1;	edit_right = vcfg.columns;

   vcolor(edit_color); cls();
	showmenu(mainmenu,&mnucolors,FALSE);
	initstatus(vcfg.rows,status_color);
	hookints(&msgcolors);
	helpopen(hlp_file,&msgcolors,&msgcolors);
	installclock(1,vcfg.columns - 7,mnucolors.normal);

	if(fname == NULL)
		new_file(fname);
	else
		load_file(fname);

	while(mainloop) {

		key = runmenu(mainmenu,&mnucolors,&mnucolors);
		
		/* Some desired key combinations are not mapped to ASCII
		or extended ASCII characters. Therefore, the state of the
		shift keys needs to be checked for these key combinations.
		These include SHIFT+UP, DOWN, LEFT, and RIGHT combinations- W. Jones. */
		/* http://webpages.charter.net/danrollins/techhelp/0055.HTM */
		
		/* Due to the way the keyboard hardware handles CTRL-INS,
		supporting the old MSDOS-style edit shortcuts is difficult in
		the context of this application. */
		#ifdef EXT_ED
			shiftkeys = kbdshift();
			discard_hilite = TRUE;
		#endif

		switch(key) {
			case 0x00:	/* key processed by runmenu() */
				break;
			case LEFT_KEY:          /* cursor left */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
					{
						discard_hilite = FALSE; /* This might work. */
						break;
					}
					else
				#endif
				
				cursor_left();
				break;
			case RIGHT_KEY:         /* cursor right */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break;
					else
				#endif
				
				cursor_right();
				break;
			case HOME_KEY:          /* start of line */
				line_ndx = 0;
				update_cursor(TRUE);
				break;
			case END_KEY:           /* end of line */
				line_ndx = curr_line->len;
				update_cursor(TRUE);
				break;
			case UP_KEY:            /* cursor up */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break;
					else
				#endif
				
				cursor_up();
				break;
			case DOWN_KEY:          /* cursor down */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break;
					else
				#endif
				
				cursor_down();
				break;
			case PGUP_KEY:          /* page up */
				page_up();
				break;
			case PGDN_KEY:          /* page down */
				page_down();
				break;
			case CTRL_PGUP:         /* start of file */
			case CTRL_HOME:
				file_home();
				break;
			case CTRL_PGDN:         /* end of file */
			case CTRL_END:
				file_end();
				break;
			case DELETE_KEY:        /* delete */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break;
					else
				#endif
				
				delete_char();
				break;
			case BACKSPACE_KEY:     /* backspace */
				backspace_char();
				break;
			case CTRL_Y:            /* delete line */
				delete_line();
				break;
			case ENTER_KEY:         /* create new line */
				split_line();
				break;
			case INSERT_KEY:        /* toggle insert */
				#ifdef EXT_ED
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break;
					else
				#endif
				insert_mode = !(insert_mode);
				show_status();
				break;
			case F1_KEY:
				helprun(hlp_general);
				break;
			case CTRL_F1:				/* help index */
				help_index();
				break;
			case F2_KEY:            /* save file */
				filesave();
				break;
			case CTRL_F2:				/* save file as */
				filesaveas();
				break;
			case F3_KEY:
				repeatsearch();		/* repeat last find */
				break;
			case F4_KEY:				/* find */
				search();
				break;
			case F9_KEY:				/* print */
				fileprint();
				break;
			case F10_KEY:				/* exit */
				terminate();
				break;
			#ifdef EXT_ED
				case CTRL_Z:
					if(shiftkeys & (SHIFT_RSHIFT | SHIFT_LSHIFT))
						break; /* Redo */
					else
						break; /* Undo */
				case CTRL_X: /* Cut */
					break; 
				case CTRL_C: /* Copy */
					break;
				case CTRL_V: /* Paste */
					break;
			
			#endif
				
			default:
				key &= 0xFF;
				if(isprint(key) || key == '\t')
					insert_char((char)key);
				else
					beep();
		}
		#ifdef EXT_ED
			/* Handle the hilite buffer. */
			/* Also handle undo-redo */
		#endif
	}
	removeclock();

	/* restore screen */
	vcolor(foreback(WHITE,BLACK));
	cls();

	return(0);

} /* main */
